/* encdec - encode and decode integers, times, and
 * internationalized strings to and from popular binary formats
 * http://www.ioplex.com/~miallen/encdec/
 * Copyright (c) 2003 Michael B. Allen <mballen@erols.com>
 *
 * The GNU Library General Public License
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 * 
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA
 */

package jcifs.util;


import java.io.IOException;
import java.util.Date;

import jcifs.SmbConstants;


@SuppressWarnings ( "javadoc" )
public final class Encdec {

    private static final long SEC_BETWEEEN_1904_AND_1970 = 2082844800L;
    private static final int TIME_1970_SEC_32BE = 1;
    private static final int TIME_1970_SEC_32LE = 2;
    private static final int TIME_1904_SEC_32BE = 3;
    private static final int TIME_1904_SEC_32LE = 4;
    private static final int TIME_1601_NANOS_64LE = 5;
    private static final int TIME_1601_NANOS_64BE = 6;
    private static final int TIME_1970_MILLIS_64BE = 7;
    private static final int TIME_1970_MILLIS_64LE = 8;


    /**
     * 
     */
    private Encdec () {}


    /*
     * Encode integers
     */

    public static int enc_uint16be ( short s, byte[] dst, int di ) {
        dst[ di++ ] = (byte) ( ( s >> 8 ) & 0xFF );
        dst[ di ] = (byte) ( s & 0xFF );
        return 2;
    }


    public static int enc_uint32be ( int i, byte[] dst, int di ) {
        dst[ di++ ] = (byte) ( ( i >> 24 ) & 0xFF );
        dst[ di++ ] = (byte) ( ( i >> 16 ) & 0xFF );
        dst[ di++ ] = (byte) ( ( i >> 8 ) & 0xFF );
        dst[ di ] = (byte) ( i & 0xFF );
        return 4;
    }


    public static int enc_uint16le ( short s, byte[] dst, int di ) {
        dst[ di++ ] = (byte) ( s & 0xFF );
        dst[ di ] = (byte) ( ( s >> 8 ) & 0xFF );
        return 2;
    }


    public static int enc_uint32le ( int i, byte[] dst, int di ) {
        dst[ di++ ] = (byte) ( i & 0xFF );
        dst[ di++ ] = (byte) ( ( i >> 8 ) & 0xFF );
        dst[ di++ ] = (byte) ( ( i >> 16 ) & 0xFF );
        dst[ di ] = (byte) ( ( i >> 24 ) & 0xFF );
        return 4;
    }


    /*
     * Decode integers
     */

    public static short dec_uint16be ( byte[] src, int si ) {
        return (short) ( ( ( src[ si ] & 0xFF ) << 8 ) | ( src[ si + 1 ] & 0xFF ) );
    }


    public static int dec_uint32be ( byte[] src, int si ) {
        return ( ( src[ si ] & 0xFF ) << 24 ) | ( ( src[ si + 1 ] & 0xFF ) << 16 ) | ( ( src[ si + 2 ] & 0xFF ) << 8 ) | ( src[ si + 3 ] & 0xFF );
    }


    public static short dec_uint16le ( byte[] src, int si ) {
        return (short) ( ( src[ si ] & 0xFF ) | ( ( src[ si + 1 ] & 0xFF ) << 8 ) );
    }


    public static int dec_uint32le ( byte[] src, int si ) {
        return ( src[ si ] & 0xFF ) | ( ( src[ si + 1 ] & 0xFF ) << 8 ) | ( ( src[ si + 2 ] & 0xFF ) << 16 ) | ( ( src[ si + 3 ] & 0xFF ) << 24 );
    }


    /*
     * Encode and decode 64 bit integers
     */

    public static int enc_uint64be ( long l, byte[] dst, int di ) {
        enc_uint32be((int) ( l & 0xFFFFFFFFL ), dst, di + 4);
        enc_uint32be((int) ( ( l >> 32L ) & 0xFFFFFFFFL ), dst, di);
        return 8;
    }


    public static int enc_uint64le ( long l, byte[] dst, int di ) {
        enc_uint32le((int) ( l & 0xFFFFFFFFL ), dst, di);
        enc_uint32le((int) ( ( l >> 32L ) & 0xFFFFFFFFL ), dst, di + 4);
        return 8;
    }


    public static long dec_uint64be ( byte[] src, int si ) {
        long l;
        l = dec_uint32be(src, si) & 0xFFFFFFFFL;
        l <<= 32L;
        l |= dec_uint32be(src, si + 4) & 0xFFFFFFFFL;
        return l;
    }


    public static long dec_uint64le ( byte[] src, int si ) {
        long l;
        l = dec_uint32le(src, si + 4) & 0xFFFFFFFFL;
        l <<= 32L;
        l |= dec_uint32le(src, si) & 0xFFFFFFFFL;
        return l;
    }


    /*
     * Encode floats
     */

    public static int enc_floatle ( float f, byte[] dst, int di ) {
        return enc_uint32le(Float.floatToIntBits(f), dst, di);
    }


    public static int enc_floatbe ( float f, byte[] dst, int di ) {
        return enc_uint32be(Float.floatToIntBits(f), dst, di);
    }


    /*
     * Decode floating point numbers
     */

    public static float dec_floatle ( byte[] src, int si ) {
        return Float.intBitsToFloat(dec_uint32le(src, si));
    }


    public static float dec_floatbe ( byte[] src, int si ) {
        return Float.intBitsToFloat(dec_uint32be(src, si));
    }


    /*
     * Encode and decode doubles
     */

    public static int enc_doublele ( double d, byte[] dst, int di ) {
        return enc_uint64le(Double.doubleToLongBits(d), dst, di);
    }


    public static int enc_doublebe ( double d, byte[] dst, int di ) {
        return enc_uint64be(Double.doubleToLongBits(d), dst, di);
    }


    public static double dec_doublele ( byte[] src, int si ) {
        return Double.longBitsToDouble(dec_uint64le(src, si));
    }


    public static double dec_doublebe ( byte[] src, int si ) {
        return Double.longBitsToDouble(dec_uint64be(src, si));
    }


    /*
     * Encode times
     */

    public static int enc_time ( Date date, byte[] dst, int di, int enc ) {
        long t;

        switch ( enc ) {
        case TIME_1970_SEC_32BE:
            return enc_uint32be((int) ( date.getTime() / 1000L ), dst, di);
        case TIME_1970_SEC_32LE:
            return enc_uint32le((int) ( date.getTime() / 1000L ), dst, di);
        case TIME_1904_SEC_32BE:
            return enc_uint32be((int) ( ( date.getTime() / 1000L + SEC_BETWEEEN_1904_AND_1970 ) & 0xFFFFFFFF ), dst, di);
        case TIME_1904_SEC_32LE:
            return enc_uint32le((int) ( ( date.getTime() / 1000L + SEC_BETWEEEN_1904_AND_1970 ) & 0xFFFFFFFF ), dst, di);
        case TIME_1601_NANOS_64BE:
            t = ( date.getTime() + SmbConstants.MILLISECONDS_BETWEEN_1970_AND_1601 ) * 10000L;
            return enc_uint64be(t, dst, di);
        case TIME_1601_NANOS_64LE:
            t = ( date.getTime() + SmbConstants.MILLISECONDS_BETWEEN_1970_AND_1601 ) * 10000L;
            return enc_uint64le(t, dst, di);
        case TIME_1970_MILLIS_64BE:
            return enc_uint64be(date.getTime(), dst, di);
        case TIME_1970_MILLIS_64LE:
            return enc_uint64le(date.getTime(), dst, di);
        default:
            throw new IllegalArgumentException("Unsupported time encoding");
        }
    }


    /*
     * Decode times
     */

    public static Date dec_time ( byte[] src, int si, int enc ) {
        long t;

        switch ( enc ) {
        case TIME_1970_SEC_32BE:
            return new Date(dec_uint32be(src, si) * 1000L);
        case TIME_1970_SEC_32LE:
            return new Date(dec_uint32le(src, si) * 1000L);
        case TIME_1904_SEC_32BE:
            return new Date( ( ( dec_uint32be(src, si) & 0xFFFFFFFFL ) - SEC_BETWEEEN_1904_AND_1970 ) * 1000L);
        case TIME_1904_SEC_32LE:
            return new Date( ( ( dec_uint32le(src, si) & 0xFFFFFFFFL ) - SEC_BETWEEEN_1904_AND_1970 ) * 1000L);
        case TIME_1601_NANOS_64BE:
            t = dec_uint64be(src, si);
            return new Date(t / 10000L - SmbConstants.MILLISECONDS_BETWEEN_1970_AND_1601);
        case TIME_1601_NANOS_64LE:
            t = dec_uint64le(src, si);
            return new Date(t / 10000L - SmbConstants.MILLISECONDS_BETWEEN_1970_AND_1601);
        case TIME_1970_MILLIS_64BE:
            return new Date(dec_uint64be(src, si));
        case TIME_1970_MILLIS_64LE:
            return new Date(dec_uint64le(src, si));
        default:
            throw new IllegalArgumentException("Unsupported time encoding");
        }
    }


    public static int enc_utf8 ( String str, byte[] dst, int di, int dlim ) {
        int start = di, ch;
        int strlen = str.length();

        for ( int i = 0; di < dlim && i < strlen; i++ ) {
            ch = str.charAt(i);
            if ( ( ch >= 0x0001 ) && ( ch <= 0x007F ) ) {
                dst[ di++ ] = (byte) ch;
            }
            else if ( ch > 0x07FF ) {
                if ( ( dlim - di ) < 3 ) {
                    break;
                }
                dst[ di++ ] = (byte) ( 0xE0 | ( ( ch >> 12 ) & 0x0F ) );
                dst[ di++ ] = (byte) ( 0x80 | ( ( ch >> 6 ) & 0x3F ) );
                dst[ di++ ] = (byte) ( 0x80 | ( ( ch >> 0 ) & 0x3F ) );
            }
            else {
                if ( ( dlim - di ) < 2 ) {
                    break;
                }
                dst[ di++ ] = (byte) ( 0xC0 | ( ( ch >> 6 ) & 0x1F ) );
                dst[ di++ ] = (byte) ( 0x80 | ( ( ch >> 0 ) & 0x3F ) );
            }
        }

        return di - start;
    }


    public static String dec_utf8 ( byte[] src, int si, int slim ) throws IOException {
        char[] uni = new char[slim - si];
        int ui, ch;

        for ( ui = 0; si < slim && ( ch = src[ si++ ] & 0xFF ) != 0; ui++ ) {
            if ( ch < 0x80 ) {
                uni[ ui ] = (char) ch;
            }
            else if ( ( ch & 0xE0 ) == 0xC0 ) {
                if ( ( slim - si ) < 2 ) {
                    break;
                }
                uni[ ui ] = (char) ( ( ch & 0x1F ) << 6 );
                ch = src[ si++ ] & 0xFF;
                uni[ ui ] |= ch & 0x3F;
                if ( ( ch & 0xC0 ) != 0x80 || uni[ ui ] < 0x80 ) {
                    throw new IOException("Invalid UTF-8 sequence");
                }
            }
            else if ( ( ch & 0xF0 ) == 0xE0 ) {
                if ( ( slim - si ) < 3 ) {
                    break;
                }
                uni[ ui ] = (char) ( ( ch & 0x0F ) << 12 );
                ch = src[ si++ ] & 0xFF;
                if ( ( ch & 0xC0 ) != 0x80 ) {
                    throw new IOException("Invalid UTF-8 sequence");
                }
                uni[ ui ] |= ( ch & 0x3F ) << 6;
                ch = src[ si++ ] & 0xFF;
                uni[ ui ] |= ch & 0x3F;
                if ( ( ch & 0xC0 ) != 0x80 || uni[ ui ] < 0x800 ) {
                    throw new IOException("Invalid UTF-8 sequence");
                }
            }
            else {
                throw new IOException("Unsupported UTF-8 sequence");
            }
        }

        return new String(uni, 0, ui);
    }


    public static String dec_ucs2le ( byte[] src, int si, int slim, char[] buf ) {
        int bi;

        for ( bi = 0; ( si + 1 ) < slim; bi++, si += 2 ) {
            buf[ bi ] = (char) dec_uint16le(src, si);
            if ( buf[ bi ] == '\0' ) {
                break;
            }
        }

        return new String(buf, 0, bi);
    }
}
